The Raspberry, Yocto Project and The TPM

Warning

I'm updating the article constantly to make it more readable and easy to reproduce.

Road Map :

-> Figure out how to use tpm2-pkcs11

-> Try to follow this article : https://connect.ed-diamond.com/GNU-Linux-Magazine/GLMF-221/U-Boot-a-la-decouverte-du-demarrage-verifie

Last update : 01/18

Overview

In the cybersecurity field we need to play with crypto primitives. It allows us to authenticate for services (ssh, vpn ...), encrypt files for confidentiality, sign mail for proving your identity to the recipient, and even securing the boot of a complex device ...

So you do need to store keys and use crypto algorithms such as RSA, ECDH, AES compliant with some criteria (industry, military, medical ...).

In this article we will create our own distro using Yocto for a RaspberryPi3 coupled with a TPM2.0 that I just purchased few days ago.

Note

If you have any question or suggestion, you can contact me at pierre[dot]ftn[at]pfontaine[dot]fr. Also, here is a repo with some sources https://gitlab.com/PierreFontaine/meta-slb9670-rpi/tree/zeus.

By abuse of language I will write "Yocto" instead of "Yocto Project".

Prerequisites

To follow this article, it will be easier if you have some knowledge on how U-Boot works, the concept behind Device Tree, the concept behind modules, and last but not least if you have already been playing with Yocto.

My Machine

To work on this side project, I used my workstation with Fedora 31. Since this is not a supported Linux Distribution (check this page : https://www.yoctoproject.org/docs/3.0/ref-manual/ref-manual.html#detailed-supported-distros) I ran everything in a Docker Container (check the previous article !).

Also, I've been building everything on my external SSD (it works well !). There is one thing to do correctly, use a supported file system (Ext4 is fine) !

Small Yocto Distro

A TPM module can natively be used with a Linux Kernel greater than 4.14.85 according to the datasheet. Hence we have to select the right branch of Poky when cloning the project using git. We have to search which branch of the meta-raspberrypi is based on a kernel greater than the 4.14.85.

So, my first attempt was to use the branch named Rocko, but looking at the Linux Version used, I was clearly on the wrong path.

Here is a link describing the release activity for the Yocto Project : https://wiki.yoctoproject.org/wiki/Releases. And everything should be right for development that started after the Warrior release.

Adding Extra Layers

We want our distro supports our RaspberryPi3, so we need to clone the Board Support Package named meta-raspberrypi. This BSP Meta depends on the meta-openembedded, so we have to clone this one too.

git clone -b zeus git://git.openembedded.org/meta-openembedded
git clone -b zeus https://github.com/agherzan/meta-raspberrypi.git

Let's clone also the meta-security layer and we will be done with cloning !

git clone -b zeus git://git.yoctoproject.org/meta-security

Also, we create our own layer to bring some Kernel support and add some features.

bitbake-layers create-layer meta-slb9670-rpi

Then, we integrate those different layers using the bitbake-layers command.

Note

For the next commands I move into the build_rpi directory.

bitbake-layers add-layer ../meta-openembedded/meta-oe
bitbake-layers add-layer ../meta-openembedded/meta-python
bitbake-layers add-layer ../meta-openembedded/meta-multimedia
bitbake-layers add-layer ../meta-openembedded/meta-networking
bitbake-layers add-layer ../meta-raspberrypi
bitbake-layers add-layer ../meta-security/meta-tpm
bitbake-layers add-layer ../meta-slb9670-rpi

Edit the Configuration File

In the build_rpi directory, you will find the conf/local.conf file. Edit this file with your favorite editor and add the following lines :

MACHINE ??= "raspberrypi3"
ENABLE_UART = "1"
PARALLEL_MAKE = "-j 12"
RPI_USE_U_BOOT = "1"
ENABLE_SPI_BUS = "1"
RPI_EXTRA_CONFIG = "dtoverlay=letstrust-tpm"

MACHINE_FEATURES_append = " tpm tpm2"

DISTRO_FEATURES_append = " systemd tpm tpm2 "
VIRTUAL-RUNTIME_init_manager = "systemd"
DISTRO_FEATURES_BACKFILL_CONSIDERED = "sysvinit"
VIRTUAL-RUNTIME_initscripts = ""

Here, we told bitbake that:

  • we want to build the project for the RaspberryPi3
  • we want to enable the UART, actually I'm using an USB/UART cable plugged on the GPIO (gnd, Rx, Tx).
  • we want to significantly improve our build process by using all available ressources, you should change this line if you don't have 6 physical cores. (hint : lscpu)
  • we want to use the Das U-Boot bootloader, it will surely be quite usefull for futur project in order to create a secure boot (don't know yet).
  • we want to enable the SPI Bus, since the TPM Module communicates using this kind of bus that seems a pretty obvious move.
  • we want to add our overlay named "letstrust-tpm", it will be explained later but in order to tell our Kernel that the TPM Module is an external Module we'll need to provide a Device Tree Source.
  • we want to add the tpm and tpm2 feature. (Maybe the tpm2 is enough to make things work).

Back on Our Own Layer

Remember that I have created a layer named meta-slb9670-rpi.

The first thing I have done is to create a recipes-core/images/core-image-slb9670-rpi.bb file, and add the following lines :

include recipes-core/images/core-image-base.bb

LICENSE = "MIT"
DESCRIPTION = "A Core image based on core-image-base for rpi"

IMAGE_FEATURES_append = " ssh-server-dropbear "

IMAGE_INSTALL_append = " tpm2-tools libtss2 libtss2-tcti-device libtss2-tcti-mssim tpm2-abrmd tpm2-pkcs11 slb9670"

KERNEL_DEVICETREE =+ "overlays/letstrust-tpm.dtbo"

Warning

include recipes-core/images/core-image-minimal.bb will create an image without any modules, however we do need them ! So base this image on core-image-base.

Here we also include some stuff from the meta-tpm allowing us to communicate with the module. At the end of IMAGE_INSTALL_append, we added slb9670, this recipes will be added in a next section.

Then, we need to modify the kernel to add some modules and the device tree source. A folder named recipes-kernel/linux is required. The last line with KERNEL_DEVICETREE statement depend on the next part, it allows to add the compiled Device Tree to the SdImage else it would be compiled but left in our deploy/image/raspberrypi3 folder.

Add Device Tree Source

Previously, in the configuration file located at poky/build_rpi/conf/local.conf we added a line "dtoverlay=letstrust-tpm". Our first step is to find the Device Tree Source working with this module. Here is the link of the file : https://letstrust.de/uploads/letstrust-tpm-overlay.dts, you can download it using wget in the Poky directory.

So move to the Poky directory to execute the following command. It will take the file that we have just downloaded and create specific .bbappend in meta-slb9670-rpi/recipes-kernel/linux/.

recipetool appendsrcfile -wm raspberrypi3 meta-slb9670-rpi/ virtual/kernel ./letstrust-tpm-overlay.dts 'arch/${ARCH}/boot/dts/overlays/letstrust-tpm-overlay.dts'

You can observe that the execution of this command create a file in recipes-kernel-linux named linux-raspberrypi_%.bbappend and a folder named linux-raspberrypi.

Inside linux-raspberrypi_%.bbappend you may have :

FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"

PACKAGE_ARCH = "raspberrypi3"

SRC_URI += "file://letstrust-tpm-overlay.dts;subdir=git/arch/${ARCH}/boot/dts/overlays"

And we will add just one line :

KERNEL_DEVICETREE += "overlays/letstrust-tpm.dtbo"

You can notice that this line is the same than the one added in our core-image-slb9670-rpi.bb.

Add Kernel Configurations

I based my configuration on this script : https://github.com/PaulKissinger/LetsTrust/blob/master/Scripts/How%20to%20patch%20a%20kernel/3_build_kernel.sh

Move to meta-slb9670-rpi/recipes-kernel/linux/ and create a folder named files. Then in this folder create a file named 9670.cfg and add those lines :

CONFIG_HW_RANDOM_TPM=m
CONFIG_TCG_TPM=m
CONFIG_TCG_TIS_CORE=m
CONFIG_TCG_TIS_SPI=m
CONFIG_SECURITYFS=y
CONFIG_TCG_TIS=m
CONFIG_TCG_TIS_I2C_ATMEL=n
CONFIG_TCG_TIS_I2C_INFINEON=n
CONFIG_TCG_TIS_I2C_NUVOTON=n
CONFIG_TCG_ATMEL=n
CONFIG_TCG_VTPM_PROXY=n
CONFIG_TCG_TIS_ST33ZP24_I2C=n
CONFIG_TCG_TIS_ST33ZP24_SPI=n
CONFIG_TRUSTED_KEYS=m

With those configurations we can choose what we want to be used as module (m), what we want to be compiled in the kernel (y) and what should not be used (n).

Now, we need to tell bitbake that those configurations have to be used when compiling the kernel for our RaspberryPi3 machine. Stay in meta-slb9670-rpi/recipes-kernel/linux/ and create a file named linux-raspberrypi_4.%.bbappend. I added 2 lines for this file :

FILESEXTRAPATHS_prepend := "${THISDIR}/files:"
SRC_URI += "file://9670.cfg"

Warning

The name of the file is important ! The name of the kernel used for the RaspberryPi is linux-raspberrypi and the "%" is a wildcard that say "It's ok for every minors of the 4th version".

Create a Service for Enabling TPM at Boot Time

For instant we would have to manually instantiate with modprobe the TPM module to start using it.

modprobe tpm_tis_spi

However, here is a workaround which implies to create a new recipe. I choose to name this one recipes-services under meta-slb9670-rpi for adding some stuff.

mkdir recipes-services
cd recipes-services

In this folder let's create a folder named files which will contain our slb9670-rpi.service.

mkdir files
touch files/slb9670-rpi.service

In this .service file add those following lines :

[Unit]
Description=Service Systemd launching TPM communication via modprobe

[Service]
User=root
Type=simple
ExecStart=modprobe tpm_tis_spi
Restart=always

[Install]
WantedBy=multi-user.target

Go back to the recipes-services folder and create the slb9670_0.1.bb script. Its purpose will be to copy our .service in the right folder.

SUMMARY = "Install a service that instantiate the communication between the raspberry and the TPM SLB9670"

LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"

SRC_URI = "file://slb9670-rpi.service"

do_install() {
    install -d ${D}${systemd_system_unitdir}
    install -d ${D}${sysconfdir}/systemd/system/multi-user.target.wants
    install -m 0644 ${WORKDIR}/slb9670-rpi.service ${D}${systemd_system_unitdir}
    ln -s ${D}${systemd_system_unitdir}/slb9670-rpi.service ${D}${sysconfdir}/systemd/system/multi-user.target.wants
}

FILES_${PN} += "${systemd_system_unitdir}/slb9670-rpi.service"

REQUIRED_DISTRO_FEATURES = "systemd"

The script above is quite basic, I added a small workaround with the symbolic link. I don't want the user to run systemctl [start|enable] slb9670-rpi, so it basically do the thing done by the systemctl enable command.

Later, when you will boot your system for the first time you will see that the services has started in the middle of the others services :

[  OK  ] Started Serial Getty on ttyS0.
[  OK  ] Reached target Login Prompts.
[  OK  ] Started Service Systemd la…PM communication via modprobe.
[    8.995529] Bluetooth: Core ver 2.22
[    9.000853] NET: Registered protocol family 31
[    9.005534] Bluetooth: HCI device and connection manager initialized
[    9.012311] Bluetooth: HCI socket layer initialized
[    9.016614] tpm_tis_spi spi0.1: 2.0 TPM (device-id 0x1B, rev-id 22)

Compile Time

Now that everything is set, let's move to the build_rpi folder and execute :

bitbake core-image-slb9670-rpi

Once bitbake has done its work you can move to build_rpi/tmp/deploy/images/raspberrypi3 and execute your favorite command to copy the core-image-slb9670-rpi-raspberrypi3.rpi-sdimg on your formated SD card !

dd if=core-image-slb9670-rpi-raspberrypi3.rpi-sdimg of=/dev/mmcblk0 bs=1MB
sync

Check the Contents of the Image

Now you can mount the SD Card on your computer, and check the raspberrypi partition which contains configuration files, and .dbt files and the overlays folder. So we can check that the overlay is added to the config.txt file.

[pfontaine@precision raspberrypi]$ cat config.txt | grep dtoverlay
dtoverlay=vc4-kms-v3d
dtoverlay=letstrust-tpm

And we can check if the letstrust-tpm.dtbo is present in the overlays folder.

[pfontaine@precision raspberrypi]$ ls overlays/ | grep letstrust-tpm.dtbo
letstrust-tpm.dtbo

You can unmount the SD Card and eject it safely to proceed the next step.

Put the SD Card in the RaspberryPi and Start the System

Although I use the UART USB cable, HDMI cable + screen will do the job.

alternate text

The placement of the TPM Module is explained in the datasheet.

So insert the SD Card in the slot, open a terminal and a Serial tool like picocom :

sudo picocom -b 115200 -l /dev/ttyUSB0

Then power on the Raspberry and you will see the boot sequence which consist of U-Boot lines, followed by Systemd lines and then a prompt asking your login. Since we are in debug mode, we can use the root user without password.

At this step if we check for available devices in /dev we won't be able to see our /dev/tpm0.

Test the TPM

Analysing the Communication

How do we prove that, when playing with tpm2 commands we do talk to the TPM ? We know that we are facing a module that can communicate through SPI and hopefully I've got a logic analyzer ! On the image below you will find how I connected my BitScope's probes between RaspberryPi and the TPM.

alternate text

Then I launched the BitScope Logic application on my computer and setted it to perform a capture when I launch one of the command supposed to communicate with the TPM.

tpm2_readpublic -c key.ctx --format pem -o key-pub.pem
alternate text

For now it only show that that there is a communication between our two devices. Packet decoded are not shown here because I'm still having errors on this side.

A Simple Example

Now that our distro support the TPM, we can use utilities from the meta-tpm that we have added.

For more information about the command, and when using the zeus branch, you should refer to the documentation for the version 3.x.x.

root@raspberrypi3:~# tpm2
tpm2-abrmd               tpm2_hash                tpm2_pcrlist
tpm2_activatecredential  tpm2_hmac                tpm2_quote
tpm2_certify             tpm2_listpersistent      tpm2_rc_decode
tpm2_create              tpm2_load                tpm2_readpublic
tpm2_createpolicy        tpm2_loadexternal        tpm2_rsadecrypt
tpm2_createprimary       tpm2_makecredential      tpm2_rsaencrypt
tpm2_dictionarylockout   tpm2_nvdefine            tpm2_send
tpm2_encryptdecrypt      tpm2_nvlist              tpm2_sign
tpm2_evictcontrol        tpm2_nvread              tpm2_startup
tpm2_getcap              tpm2_nvreadlock          tpm2_takeownership
tpm2_getmanufec          tpm2_nvrelease           tpm2_unseal
tpm2_getpubak            tpm2_nvwrite             tpm2_verifysignature
tpm2_getpubek            tpm2_pcrevent
tpm2_getrandom           tpm2_pcrextend

So, as a hello world test we can for example compute the digest of the .ash_history file which contains the last command you have done.

root@raspberrypi3:~# tpm2_hash .ash_history
hash(sha1):b46eb015afa64feaf7caf10a4627d1c291b76384
root@raspberrypi3:~# ls
root@raspberrypi3:~# tpm2_hash .ash_history
hash(sha1):c635711d208d1e65c187a7c4dd22ff1bcb153206

More Complex Example

The following example is inspired from the presentation made by Philip Tricca for the Linux Security Summit : "Getting Started with the TPM2 Software Stack (TSS2) - Philip Tricca, Intel".

Create a context file using the command below, you can specify :

  • the target hierarchy (o, p, e or n) with the option "-H"
  • the hash algorithm for generating the object name with the option "-g"
  • the algorithm used for generating the primary key with the option "-G"
  • the output file for storing the context with the option "-C"
tpm2_startup -c
tpm2_takeownership -c
tpm2_createprimary -H o -g sha256 -G rsa -C primary.ctx

> ObjectAttribute: 0x00030072
> CreatePrimary Succeed ! Handle: 0x800000ff

Note

Hierarchy

  • o : TPM_RH_OWNER
    Also known as Storage Hierarchy, this one is intended to be used by the end user. Data persist through reboot.
  • p : TPM_RH_PLATFORM
    The platform hierarchy is intended to be under the control of the platform manufacturer. Data persist through reboot.
  • e : TPM_RH_ENDORSEMENT
    The Endorsement hierarchy should be used for high sensitive value, and is intended for the end user. Flag, policy and authorization value are independant from the other hierarchies. Data persist through reboot.
  • n : TPM_RH_NULL
    The Null hierarchy can be used for storing primary key. However value does not persist through boot, hence it mostly use for sessions or as a crypto co processor.

For more information see the book "A Practical Guide to TPM2.0" Chapter 9.

The tpm2_createprimary command has created a file primary.ctx

ls
> primary.ctx

With the version 3.0.X we have the choice between those algorithms :

For this example, I suggest to use the default one which is RSA, this way we can create a pair of public and private key :

tpm2_create -c primary.ctx -g sha256 -Grsa -u key.pub -r key.priv
> algorithm:
     value: sha256
     raw: 0xb
  attributes:
     value: fixedtpm|fixedparent|sensitivedataorigin|userwithauth|decrypt|sign
     raw: 0x60072
  type:
     value: rsa
     raw: 0x1
     rsa: a4f10e2a5c4ef1f48333875cb8fc7ff32c03e0a5593085fc88ef06520f43c974a88bc58a08af414e90090725716403003a516a4171be8a350618aa6e6cf157e068de57bea0536a2f9f5da2916b1835fb7cf47a4672bc3384030b072bde294e18bea1bd2b91f1924855437c133fada8bf762f4055ae4a7f1f272e11961940d34f1669634229fac565114f8420bf0cf72c47ea99c8abbbb82011e9cce96558cad979b45d525b7a515960a5625cbab5a4175db7b5124955c31e4f9c2e846610b0587c448c827570ba3c2365d1cd4d5e8552842bbbe053bf337e00b3e9db7d1201397aa7d88 22bcb944286f58bbda5d8654cda3779b28a9ca24e48cfa4a218bd879

Then, once the key are correctly generated, we want them to be loaded into the TPM module so we can start using them. To do so, we use the tpm2_load command as shown below and we attach a context file.

tpm2_load -c primary.ctx -u key.pub -r key.priv -C key.ctx

Now, the next step would be to share our public key. Hence, we will create a PEM file that can be understood by OpenSSL.

tpm2_readpublic -c key.ctx --format pem -o key-pub.pem

> name: 000b3e0356d7b3f72d6ffa699ab337899a6a44bd5246116d99f4196c7634ff8b3cea
  qualified name: 000b70edf26adc3ce18438b0baef47405a8c9ed0d1c049e5bc83d166297f191e3e6e
  algorithm:
    value: sha256
    raw: 0xb
  attributes:
    ...

We have generated the key-pub.pem file, we can check that the output is in the right format with the cat command.

cat key-pub.pem
> -----BEGIN PUBLIC KEY-----
  MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApPEOKlxO8fSDM4dcuPx/
  8ywD4KVZMIX8iO8GUg9DyXSoi8WKCK9BTpAJByVxZAMAOlFqQXG+ijUGGKpubPFX
  4GjeV76gU2ovn12ikWsYNft89HpGcrwzhAMLByveKU4YvqG9K5HxkkhVQ3wTP62o
  v3Zi9AVa5Kfx8nLhGWGUDTTxZpY0Ip+sVlEU+EIL8M9yxH6pnIq7u4IBHpzOllWM
  rZebRdUlt6UVlgpWJcurWkF123tRJJVcMeT5wuhGYQsFh8RIyCdXC6PCNl0c1NXo
  VShCu74FO/M34As+nbfRIBOXqn2IIry5RChvWLvaXYZUzaN3myipyiTkjPpKIYvY
  eQIDAQAB
  -----END PUBLIC KEY-----

I copied the public key on my laptop and created a very sensitive message, then I cipher the message with the RSA public key and send it back to the RaspberryPi using the SCP command.

touch sensitive-file.txt
echo "I'm a very sensitive message !" > sensitive-file.txt
openssl rsautl -encrypt -inkey pub.pem -pubin -in sensitive-file.txt -out sensitive-file.txt.enc
scp sensitive-file.txt.enc root@192.168.1.9:/home/root

Back to the RaspberryPi, we check that we received our encrypted file previously send via scp. We ask the TPM Module to decipher the encrypted file using the private key stored in the TPM.

ls
> key-pub.der             key.priv                sensitive-file.txt.enc
  key-pub.pem             key.pub
  key.ctx                 primary.ctx

tpm2_rsadecrypt -c key.ctx -I sensitive-file.txt.enc -o sensitive-file.txt

cat sensitive-file.txt
> I'm a very sensitive message !

To Conclude

I have learned so much doing this experimentation and hope it will help you if you are trying to implement this kind of solution ! I would like to thanks the Yocto Community through the help brougth via the mailing list, the author of letstrust.de, the author of the meta-raspberrypi and all the contributor to the stuff used in this small project.

Appendix

Hardware Tour

RaspberryPi3

The RaspberryPi3 is one of the most used development board. It can be used for embedded prototyping or even for making an office desktop.

alternate text

Well it has an HDMI port, a 64 bit CPU, a Micro SD slot, 40-pin extended GPIO for a decent price so it's quite a good choice for beginners and students. If you want more information on the specifications, you will find them here : https://www.raspberrypi.org/products/raspberry-pi-3-model-b/

Although I didn't test the following stuff on other RaspberryPi (2,4,Zero) it should work as well according to what I could have seen on some posts.

LetsTrust TPM Module

This TPM Module is based on the Infineon OPTIGA SLB 9670 TPM2.0 chip. This one is certified with Common Criteria EAL4+ and FIPS 140-2.

alternate text

This device will use 10 pins of your extended GPIO. Actually it communicates through the SPI protocol.

I bought it on the Reichelt website : https://secure.reichelt.com/fr/en/raspberry-pi-trusted-platform-module-tpm-slb9670-rpi-shd-tpm-p253834.html?&trstct=pos_0

For the Yocto beginners

Clone Poky zeus + Docker

Our first step will be to clone the Poky project where we want to build everything.

Warning

Make sure that you have enough space, since a build can consume up to 50Gb.

git clone -b zeus git://git.yoctoproject.org/poky

Once this step is done we can directly set up our container.

cd poky
docker run --rm -it -v $(pwd):$(pwd) crops/poky --workdir=$(pwd)

Bitbake and Build

For this step we will source the oe-init-build-env script. It will create a folder with the name passed as first argument, set environment variables ...

source oe-init-build-env build_rpi

Now, we have a build_rpi directory and we can now add some third party layers.